Skip to content

Initial commit#1

Open
Tom-RedCactus wants to merge 10 commits intoDeskproApps:mainfrom
redcactus-nl:feature/initial-app
Open

Initial commit#1
Tom-RedCactus wants to merge 10 commits intoDeskproApps:mainfrom
redcactus-nl:feature/initial-app

Conversation

@Tom-RedCactus
Copy link

@Tom-RedCactus Tom-RedCactus commented Feb 12, 2026

Initial code for the Bubble365 app. We just copied the template. Main thing we changed is the Main.tsx

Summary by Sourcery

Initialize the Bubble365 Deskpro app project with a React/Vite-based Deskpro app shell, CI/CD workflows, and packaging/versioning infrastructure.

New Features:

  • Add a Deskpro global app that embeds the external Bubble365 UI via an iframe and integrates with the Deskpro app client for badge, focus, and hyperlink handling.
  • Introduce a typed postMessage communication layer between the host app and the embedded Bubble365 iframe, including request/response handling and initialization messaging.

Enhancements:

  • Set up Sentry instrumentation and error boundaries for frontend error tracking and resilience.
  • Configure React Query, routing, and basic styling to support the app layout and data fetching patterns.

Build:

  • Add Vite configuration, TypeScript setup, Jest configuration, ESLint rules, and packaging scripts to build, test, lint, and bundle the app, including manifest version bumping.
  • Introduce GitHub Actions workflows for PR builds, main-branch builds with automatic tagging, demo deployments, and marketplace releases.
  • Define the Deskpro app manifest and packaging scripts to produce distributable app zip files with associated docs and assets.

Documentation:

  • Replace the placeholder README with product and development documentation, add app description and setup guides, and define security and versioning information.

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 12, 2026

Reviewer's Guide

Initial Bubble365 Deskpro app scaffold using the standard Deskpro React/Vite template, wiring a Bubble365 iframe integration with postMessage-based communication, Sentry error reporting, React Query, and setting up CI/CD workflows, testing, packaging, and manifest for deployment to the Deskpro app marketplace.

Sequence diagram for Bubble365 iframe call start request

sequenceDiagram
  actor Agent
  participant DeskproApp as DeskproApp_Main
  participant ParentService as ParentIframeService
  participant BubbleIframe as Bubble365_iframe

  Agent->>DeskproApp: Click startCall (future use)
  DeskproApp->>ParentService: sendRequest(call, start, {number})
  activate ParentService
  ParentService->>ParentService: createMessage(call, start, payload, null)
  ParentService->>BubbleIframe: postMessage(JSON.stringify(request), "*")
  deactivate ParentService

  Note over BubbleIframe: Bubble app handles call start
  BubbleIframe-->>ParentService: postMessage(JSON.stringify(response))
  activate ParentService
  ParentService->>ParentService: onMessage(event)
  ParentService->>ParentService: JSON.parse(event.data)
  ParentService->>ParentService: Match response_id in pending
  ParentService-->>DeskproApp: Resolve Promise<Message<{success, error?}>>
  deactivate ParentService

  alt success
    DeskproApp->>DeskproApp: console.log("Call started")
  else failure
    DeskproApp->>DeskproApp: console.error("Call failed", error)
  end
Loading

Sequence diagram for Bubble365 iframe initialization and UI control

sequenceDiagram
  participant ParentService as ParentIframeService
  participant BubbleIframe as Bubble365_iframe
  participant DeskproClient as DeskproAppClient

  Note over ParentService,BubbleIframe: After iframe loads, ParentIframeService is created

  BubbleIframe-->>ParentService: postMessage("{protocol:bubble365, channel:initialize, cmd:request}")
  activate ParentService
  ParentService->>ParentService: onMessage(event)
  ParentService->>BubbleIframe: sendMessage(initialize, request, {success:true, settings})
  ParentService->>DeskproClient: handlers.onInitialized()
  deactivate ParentService

  BubbleIframe-->>ParentService: postMessage("{channel:badge, cmd:set, payload:{count}}")
  activate ParentService
  ParentService->>DeskproClient: handlers.onBadgeSet(count)
  deactivate ParentService

  BubbleIframe-->>ParentService: postMessage("{channel:widget, cmd:set, payload:{visible}}")
  activate ParentService
  alt visible is true
    ParentService->>DeskproClient: handlers.onWidgetSet(true) -> focus()
  else visible is false or undefined
    ParentService->>DeskproClient: handlers.onWidgetSet(false) -> unfocus()
  end
  deactivate ParentService

  BubbleIframe-->>ParentService: postMessage("{channel:hyperlink, cmd:open, payload:{url,target}}");
  activate ParentService
  ParentService->>ParentService: handleHyperlinkOpenRequest()
  alt url is Deskpro app URL
    ParentService->>ParentService: window.parent.location.replace(url)
  else external URL
    ParentService->>ParentService: window.open(url, target or _blank)
  end
  deactivate ParentService
Loading

Class diagram for Bubble365 iframe integration and app bootstrap

classDiagram
  class ParentIframeService {
    - HTMLIFrameElement iframe
    - Map~string, function~ pending
    - ParentHandlers handlers
    - function boundOnMessage
    - Settings settings
    + ParentIframeService(iframe: HTMLIFrameElement, handlers: ParentHandlers, settings: Settings)
    + sendMessage(TPayload)(channel: PostMessageChannel, cmd: string, payload: TPayload, response_id: string) void
    + sendRequest(TRequest, TResponse)(channel: PostMessageChannel, cmd: string, payload: TRequest, timeoutMs: number) Promise~Message~TResponse~~
    + destroy() void
    - createMessage(TPayload)(channel: PostMessageChannel, cmd: string, payload: TPayload, responseId: string) Message~TPayload~
    - onMessage(event: MessageEvent) void
    - handleHyperlinkOpenRequest(request: Message~unknown~) void
  }

  class Settings {
    + override_hyperlink_open: boolean
  }

  class BadgeSetPayload {
    + count: number
  }

  class FocusPayload {
    + visible: boolean
  }

  class HyperlinkOpenPayload {
    + url: string
    + target: string
    + button: unknown
  }

  class PostMessageChannel {
  }

  class ParentHandlers {
    + onInitialized(): void
    + onBadgeSet(count: number): void
    + onWidgetSet(visible: boolean): void
    + onHyperlinkOpen(url: string, target: string, button: unknown): void
  }

  class Message~TPayload~ {
    + protocol: string
    + version: number
    + id: string
    + response_id: string
    + channel: PostMessageChannel
    + cmd: string
    + payload: TPayload
  }

  class Main {
    + clientRef: MutableRefObject
    + iframeRef: MutableRefObject
    + iframeServiceRef: MutableRefObject
    + useEffect() void
    + startCall() Promise~void~
  }

  class App {
    + App()
  }

  class queryClient {
    + QueryClient
  }

  class DeskproAppProvider {
  }

  class HashRouter {
  }

  class QueryClientProvider {
  }

  class ErrorBoundary {
  }

  class LoadingSpinner {
  }

  class SentryInit {
    + init()
  }

  ParentIframeService --> ParentHandlers : uses
  ParentIframeService --> Message : creates
  Message --> PostMessageChannel : uses
  ParentIframeService --> Settings : has
  ParentIframeService --> BadgeSetPayload : uses
  ParentIframeService --> FocusPayload : uses
  ParentIframeService --> HyperlinkOpenPayload : uses

  Main --> ParentIframeService : creates and stores
  Main --> queryClient : uses
  Main --> DeskproAppProvider : rendered within
  Main --> HashRouter : rendered within
  Main --> QueryClientProvider : rendered within

  App --> Main : routes to

  SentryInit --> ErrorBoundary : wraps React tree

  QueryClientProvider --> queryClient : provides

  class instrument_ts {
    + configureSentry()
  }

  instrument_ts --> SentryInit : calls

  class index_html {
    + root_div
  }

  App --> index_html : rendered into root_div
Loading

File-Level Changes

Change Details Files
Implement Bubble365 iframe host page and parent-iframe communication service.
  • Create ParentIframeService to wrap postMessage communication with the Bubble365 iframe, including request/response handling, initialization, badge/widget updates, and hyperlink overrides.
  • Define Message and ParentHandlers types to strongly type the iframe protocol and callbacks.
  • Implement Main page that boots the Deskpro app client, instantiates ParentIframeService for the iframe, wires badge count, focus/unfocus, and hyperlink open behavior, and exposes a (currently unused) startCall helper.
  • Add app-wide routing and page exports so Main is served as the single route.
src/types/services/parentIframeService.ts
src/types/message.ts
src/types/parentHandlers.ts
src/pages/Main/Main.tsx
src/pages/Main/index.ts
src/pages/index.ts
src/App.tsx
Set up React/Vite application entrypoint with Deskpro SDK, React Query, and Sentry error handling.
  • Add Sentry initialization based on query-string DSN/tunnel parameters.
  • Configure React 18 root with StrictMode, DeskproAppProvider, HashRouter, QueryClientProvider, Suspense, and Sentry ErrorBoundary around the App component.
  • Define a shared React Query client with default options suitable for Deskpro apps.
src/instrument.ts
src/main.tsx
src/query.ts
Configure build tooling, testing, linting, and TypeScript for the app.
  • Define Vite config with React plugin, Sentry Vite plugin (conditionally enabled), dev server settings, alias for @ to src, and rollup copy step to ship manifest, docs, and assets into dist.
  • Configure Jest with jsdom, SWC transforms, custom resolver for simplebar-react, moduleNameMapper for assets and @ aliases, coverage rules, and global setup to force UTC timezone.
  • Add TypeScript compiler options, path aliases, and Jest/Vite type definitions.
  • Set up ESLint with TypeScript rules, React hooks rules, Prettier integration, and custom rule tweaks.
  • Add simple CSS to make the root and iframe full-viewport, and placeholder App CSS.
vite.config.ts
jest.config.js
config/jest/fileTransform.js
config/jest/cssTransform.js
config/jest/global-setup.js
custom-jest-resolver.js
tsconfig.json
.eslintrc.js
src/main.css
src/App.css
Define packaging/versioning utilities and manifest for Deskpro app deployment.
  • Add manifest.json describing the Bubble365 app, global target entrypoint, and proxy whitelist for Bubble365 EU endpoints.
  • Add Node script to bump manifest.json semantic version based on major/minor labels and previous version.
  • Add packaging script that zips dist into a versioned file under build/, using slugified app name.
  • Wire npm scripts for build, packaging per environment, tests, linting, and manifest version bumping, plus dependencies/devDependencies for React, Deskpro SDK/UI, Sentry, Jest, SWC, ESLint, Vite, etc.
manifest.json
bin/bumpManifestVer.js
bin/package.js
package.json
Set up GitHub Actions workflows for build, deploy-to-demo, and release to the Deskpro marketplace.
  • Create reusable subworkflow to lint, type-check, test with coverage, bump manifest version, build, and optionally tag and push Git tags and package zip artifact.
  • Create subworkflow to deploy a PR demo to a remote Docker-based Deskpro environment, upload the app package into the container, and install it with provided settings.
  • Create subworkflow to publish a GitHub Release with the packaged app, and optionally register/trigger a release in the Deskpro apps registry when conditions are met.
  • Wire feature_build workflow for PRs/merge queue to call the build and demo deploy subworkflows, and main_merge workflow to run build+tag and release on pushes to main.
.github/workflows/subworkflow-build.yml
.github/workflows/subworkflow-deploy.yml
.github/workflows/subworkflow-release.yml
.github/workflows/feature_build.yml
.github/workflows/main_merge.yml
Add project metadata, documentation, and basic repo configuration.
  • Replace placeholder README with marketing copy, setup instructions, development/testing guide, versioning behavior, and contributor badges.
  • Add DESCRIPTION and SETUP docs used by the manifest and marketplace.
  • Add security policy, Dependabot config for monthly npm updates, and basic setup/testing helper files (env templates, devcontainer, editor/ESLint ignore, Jest setup, Babel, pnpm lock, IntelliJ run configs, git/npm ignores, codeowners, icons, mock data, etc.).
README.md
DESCRIPTION.md
SETUP.md
SECURITY.md
.github/dependabot.yml
.devcontainer/devcontainer.json
.editorconfig
.env.dev
.env.loc
.env.prod
.eslintignore
.github/CODEOWNERS
.gitignore
.npmrc
icon.svg
jest.setup.tsx
pnpm-lock.yaml
.babelrc.json
testing/index.ts
testing/mockPosts.json
.run/start.run.xml
.run/package_dev.run.xml
.run/package_loc.run.xml
.run/package_prod.run.xml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 11 issues, and left some high level feedback:

  • The postMessage bridge in ParentIframeService currently uses a wildcard targetOrigin ('*') and does not check event.origin; consider tightening this by deriving the expected origin from VITE_BUBBLE_URL and validating event.origin before processing messages to reduce the risk of handling messages from unexpected sources.
  • ParentIframeService.destroy() silently deletes all pending requests without notifying callers; it may be helpful to reject those pending Promises explicitly (e.g. by storing reject callbacks) so consumers can handle teardown-related failures instead of hanging indefinitely.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The postMessage bridge in ParentIframeService currently uses a wildcard targetOrigin ('*') and does not check event.origin; consider tightening this by deriving the expected origin from VITE_BUBBLE_URL and validating event.origin before processing messages to reduce the risk of handling messages from unexpected sources.
- ParentIframeService.destroy() silently deletes all pending requests without notifying callers; it may be helpful to reject those pending Promises explicitly (e.g. by storing reject callbacks) so consumers can handle teardown-related failures instead of hanging indefinitely.

## Individual Comments

### Comment 1
<location> `src/types/services/parentIframeService.ts:84-93` </location>
<code_context>
+    }
+  }
+
+  private onMessage(event: MessageEvent): void {
+    if (typeof event.data !== "string") return
+
+    console.debug("[ParentIframeService] Received message", event.data)
+
+    let parsed: unknown
+    try {
+      parsed = JSON.parse(event.data)
+    } catch {
+      return
+    }
+
+    const msg = parsed as Message<unknown>
+    if (!msg || msg.protocol !== "bubble365" || msg.version !== 1) return
+
</code_context>

<issue_to_address>
**🚨 issue (security):** Consider validating the message origin/source before trusting event.data.

`onMessage` only validates the payload structure and not `event.origin` or `event.source`. Another frame on the page could spoof `bubble365` messages. Please also verify `event.origin` (and/or `event.source === this.iframe.contentWindow`) matches the expected iframe before parsing/handling the message.
</issue_to_address>

### Comment 2
<location> `src/types/services/parentIframeService.ts:31-32` </location>
<code_context>
+
+  public sendMessage<TPayload>(channel: PostMessageChannel, cmd: string, payload?: TPayload, response_id?: string | null): void {
+    const message = this.createMessage(channel, cmd, payload, response_id)
+    console.debug("[ParentIframeService] Post message", message)
+    this.iframe.contentWindow?.postMessage(JSON.stringify(message), "*")
+  }
+
</code_context>

<issue_to_address>
**🚨 suggestion (security):** Using `postMessage` with `"*"` targetOrigin is permissive; consider tightening it.

Both `sendMessage` and `sendRequest` currently call `postMessage(..., "*")`. If the Bubble iframe’s origin is known, pass that explicitly instead to avoid messages being deliverable to unintended windows if the frame hierarchy changes or is reused.

Suggested implementation:

```typescript
export class ParentIframeService {
  private iframe: HTMLIFrameElement
  private pending = new Map<string, (msg: Message<unknown>) => void>()
  private targetOrigin: string

```

```typescript
    this.settings = settings

    console.debug("[ParentIframeService] starting service..")

    try {
      // Derive the Bubble iframe origin from its src (supports absolute and relative URLs)
      this.targetOrigin = new URL(this.iframe.src, window.location.href).origin
    } catch {
      // Fallback to permissive origin if parsing fails; consider tightening further if possible
      console.warn("[ParentIframeService] Unable to determine iframe origin, falling back to '*'.")
      this.targetOrigin = "*"
    }

    this.boundOnMessage = this.onMessage.bind(this)
    window.addEventListener("message", this.boundOnMessage)

```

```typescript
  public sendMessage<TPayload>(channel: PostMessageChannel, cmd: string, payload?: TPayload, response_id?: string | null): void {
    const message = this.createMessage(channel, cmd, payload, response_id)
    console.debug("[ParentIframeService] Post message", message)
    this.iframe.contentWindow?.postMessage(JSON.stringify(message), this.targetOrigin)

```

```typescript
    this.iframe.contentWindow?.postMessage(JSON.stringify(message), this.targetOrigin)

```

There is also a `sendRequest` method in this class that currently calls `postMessage(..., "*")` but is not shown in your snippet. You should update that call in the same way:

- Replace `this.iframe.contentWindow?.postMessage(JSON.stringify(message), "*")` with `this.iframe.contentWindow?.postMessage(JSON.stringify(message), this.targetOrigin)` inside `sendRequest`.

If there are any other direct `postMessage` calls targeting `"*"` in this file, they should be updated to use `this.targetOrigin` as well.
</issue_to_address>

### Comment 3
<location> `src/pages/Main/Main.tsx:7` </location>
<code_context>
+} from "@deskpro/app-sdk"
+import {ParentIframeService} from "@/types/services/parentIframeService"
+
+const BUBBLE_URL = import.meta.env.VITE_BUBBLE_URL as string
+
+export const Main = () => {
</code_context>

<issue_to_address>
**suggestion:** Add a runtime guard or sensible fallback if `VITE_BUBBLE_URL` is missing.

The `as string` cast assumes `VITE_BUBBLE_URL` is always defined. If it’s missing, the iframe ends up with `src="undefined"`, which is misleading. Add a runtime check (e.g., at startup) and either log a clear error, show a fallback UI, or skip rendering the iframe when the env var is absent.

Suggested implementation:

```typescript
const BUBBLE_URL = import.meta.env.VITE_BUBBLE_URL

export const Main = () => {
  const {client} = useDeskproAppClient()
  const clientRef = useRef<typeof client | null>(null)
  const iframeRef = useRef<HTMLIFrameElement | null>(null)
  const iframeServiceRef = useRef<ParentIframeService | null>(null)

  if (!BUBBLE_URL) {
    // Fail fast with a clear runtime signal instead of rendering a broken iframe.
    console.error(
      "[Deskpro Bubble App] Environment variable VITE_BUBBLE_URL is not defined. " +
      "The Bubble iframe cannot be rendered. Please set VITE_BUBBLE_URL in your environment."
    )

    // You can replace this with a more tailored fallback component if desired.
    return (
      <div>
        Configuration error: Bubble URL is not configured. Please contact an administrator.
      </div>
    )
  }

```

If other parts of this file (or other modules) rely on `BUBBLE_URL` being typed as `string`, you may need to:
1. Update those usages to either:
   - Use a locally narrowed variable (e.g., `const bubbleUrl = BUBBLE_URL as string` *after* the guard), or
   - Perform similar runtime guards before using `BUBBLE_URL`.
2. Ensure that the iframe `src` in this component is only ever set to a non-empty string (e.g., use `BUBBLE_URL` after this guard or pass a narrowed `bubbleUrl`).
</issue_to_address>

### Comment 4
<location> `src/pages/Main/Main.tsx:33-38` </location>
<code_context>
+      onBadgeSet: (count) => {
+        void clientRef.current?.setBadgeCount?.(count)
+      },
+      onWidgetSet: (visible) => {
+        if (visible)
+          clientRef.current?.focus()
+        else
+          clientRef.current?.unfocus()
+      },
+      onHyperlinkOpen: (url, target) => {
</code_context>

<issue_to_address>
**suggestion:** Clarify handling of undefined `visible` to avoid unintended unfocus.

Because `undefined` is treated as falsy, omitting `visible` will currently trigger `unfocus()`. If the protocol allows `visible` to be omitted, consider unfocusing only when `visible === false`, or focusing only when `visible === true` and ignoring `undefined`.

```suggestion
      onWidgetSet: (visible) => {
        if (visible === true) {
          clientRef.current?.focus()
        } else if (visible === false) {
          clientRef.current?.unfocus()
        }
      },
```
</issue_to_address>

### Comment 5
<location> `README.md:22` </location>
<code_context>
+With the Bubble365 Embedded CRM App you experience the power of the Bubble integration tool, fully integrated within your CRM environment.
+The Bubble365 Embedded CRM App is compatible with 90+ telephony platforms.
+If your telephony platform supports Call Control, you manage your softphone or connected desk phone directly from the CRM - with features such as take calls, transfer calls, put on hold and hang up.
</code_context>

<issue_to_address>
**suggestion (typo):** Add a comma after "App" for correct sentence structure.

Consider revising the sentence to: "With the Bubble365 Embedded CRM App, you experience the power of the Bubble integration tool, fully integrated within your CRM environment."

```suggestion
With the Bubble365 Embedded CRM App, you experience the power of the Bubble integration tool, fully integrated within your CRM environment.
```
</issue_to_address>

### Comment 6
<location> `README.md:27` </location>
<code_context>
+If your telephony platform supports Call Control, you manage your softphone or connected desk phone directly from the CRM - with features such as take calls, transfer calls, put on hold and hang up.
+The app provides a seamless user experience and increases productivity by bringing all communication and CRM functionalities together in one interface.
+
+**Pop-up Notification**: Instantly see all customer information on an incoming, outgoing and transferred call.
+
+**SearchBar**: Search directly in Deskpro contacts, open a customer card or send a message.
</code_context>

<issue_to_address>
**suggestion (typo):** Use plural "calls" to agree with the list of call types.

Given the list format, I suggest ending the sentence with: "on incoming, outgoing, and transferred calls."

```suggestion
**Pop-up Notification**: Instantly see all customer information on incoming, outgoing, and transferred calls.
```
</issue_to_address>

### Comment 7
<location> `README.md:40-41` </location>
<code_context>
+You can follow our [wiki guide](https://wiki.redcactus.cloud/en/crm-software/Deskpro-embedded) for a step-by-step guide to setting up the app in Deskpro.
+
+## Development
+This app was developed primarily using **Typescript**, **React**, and **Vite**.
+
+#### Setup
</code_context>

<issue_to_address>
**issue (typo):** Correct the capitalization of "TypeScript".

The official spelling is "TypeScript", so this sentence should read "**TypeScript**, **React**, and **Vite**."

```suggestion
## Development
This app was developed primarily using **TypeScript**, **React**, and **Vite**.
```
</issue_to_address>

### Comment 8
<location> `DESCRIPTION.md:1` </location>
<code_context>
+With the Bubble365 Embedded CRM App you experience the power of the Bubble integration tool, fully integrated within your CRM environment.
+The Bubble365 Embedded CRM App is compatible with 90+ telephony platforms.
+If your telephony platform supports Call Control, you manage your softphone or connected desk phone directly from the CRM - with features such as take calls, transfer calls, put on hold and hang up.
</code_context>

<issue_to_address>
**suggestion (typo):** Add a comma after "App" for grammatical correctness.

For example: "With the Bubble365 Embedded CRM App, you experience the power of the Bubble integration tool, fully integrated within your CRM environment."

```suggestion
With the Bubble365 Embedded CRM App, you experience the power of the Bubble integration tool, fully integrated within your CRM environment.
```
</issue_to_address>

### Comment 9
<location> `DESCRIPTION.md:3` </location>
<code_context>
+With the Bubble365 Embedded CRM App you experience the power of the Bubble integration tool, fully integrated within your CRM environment.
+The Bubble365 Embedded CRM App is compatible with 90+ telephony platforms.
+If your telephony platform supports Call Control, you manage your softphone or connected desk phone directly from the CRM - with features such as take calls, transfer calls, put on hold and hang up.
+The app provides a seamless user experience and increases productivity by bringing all communication and CRM functionalities together in one interface.
+
</code_context>

<issue_to_address>
**suggestion (typo):** Improve the sentence flow ("you can manage" and gerunds for call actions).

For example: “If your telephony platform supports Call Control, you can manage your softphone or connected desk phone directly from the CRM, with features such as taking calls, transferring calls, putting calls on hold, and hanging up.”

```suggestion
If your telephony platform supports Call Control, you can manage your softphone or connected desk phone directly from the CRM, with features such as taking calls, transferring calls, putting calls on hold, and hanging up.
```
</issue_to_address>

### Comment 10
<location> `DESCRIPTION.md:6` </location>
<code_context>
+If your telephony platform supports Call Control, you manage your softphone or connected desk phone directly from the CRM - with features such as take calls, transfer calls, put on hold and hang up.
+The app provides a seamless user experience and increases productivity by bringing all communication and CRM functionalities together in one interface.
+
+**Pop-up Notification**: Instantly see all customer information on an incoming, outgoing and transferred call.
+
+**SearchBar**: Search directly in Deskpro contacts, open a customer card or send a message.
</code_context>

<issue_to_address>
**suggestion (typo):** Use plural "calls" to match the multiple call types.

To align with the list of call types, consider “…on incoming, outgoing, and transferred calls.”

```suggestion
**Pop-up Notification**: Instantly see all customer information on incoming, outgoing, and transferred calls.
```
</issue_to_address>

### Comment 11
<location> `SETUP.md:5` </location>
<code_context>
+===
+To use the Bubble app, you need a license for Bubble.
+
+When installed, you can log in with your Bubble credentials into the app in the topbar.
</code_context>

<issue_to_address>
**suggestion (typo):** Slightly rephrase for smoother grammar ("log in to the app" and possibly "top bar").

Suggestion: "When installed, you can log in to the app with your Bubble credentials from the top bar."
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link

@djgadd djgadd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple minor suggestions, take or leave them, I'm not going to block the on them 👍 Thanks for the PR!

@Tom-RedCactus
Copy link
Author

Tom-RedCactus commented Feb 19, 2026

A couple minor suggestions, take or leave them, I'm not going to block the on them 👍 Thanks for the PR!

I've addressed all the suggested changes, Thanks for the review!
The PR is ready to merge from my side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants